﻿using System;
using System.Collections.Generic;
using System.Text;
using PW.Mini.SlepowronskiJ.Siec.Budowa;
using TerWoord.Diagnostics;

namespace PW.Mini.SlepowronskiJ.Siec.Algorytmy
{

    [Serializable]
    public class RPROPAlgorytmUczenia : RPROPAbstractAlgorytmUczenia
    {
        #region konstruktor

        /// <summary>
        /// tworzy nowy obiekt klasy algorytmu wstecznej propagacji bledu 
        /// z parametrami wspolczynnikUczenia i momentu pobieranymi z Monitora
        /// </summary>
        public RPROPAlgorytmUczenia()
            : base()
        {
            this.nazwa = "FF RPROP";
        }

        /// <summary>
        /// tworzy nowy obiekt klasy algorytmu wstecznej propagacji bledu dla
        /// sieci neuronowej
        /// z parametrami wspolczynnikUczenia i momentu pobieranymi z Monitora
        /// </summary>
        /// <param name="siecNeuronowa">siec neuronowa do uczenia</param>
        public RPROPAlgorytmUczenia(SiecNeuronowa siecNeuronowa)
            : base(siecNeuronowa)
        {
            this.nazwa = "FF RPROP";
        }

        public override AlgorytmUczenia UtworzNowyAlgorytm()
        {
            return new RPROPAlgorytmUczenia();
        }

        #endregion

        /// <summary>
        /// Metoda uczenia siec neuronowej z Walidacją na danych wejsciowych
        /// reprezentowanych przez macierz wartosci wraz z 
        /// oczekiwanymi wyjsciami dla tych danych 
        /// jesli wejscia lub wyjscia walidacyjne = null to bez walidacji
        /// </summary>
        /// <param name="wejscia">macierz wejsc testowych</param>
        /// <param name="wyjscia">macierz wyjsc oczekiwanych</param>
        /// <param name="wejsciaWalidacyjne">macierz wejsc walidacyjnych</param>
        /// <param name="wyjsciaWalidacyjne">macierz wyjsc walidacyjnych</param>
        public override void Uczenie(double[][] wejscia, double[][] wyjscia,
            double[][] wejsciaWalidacyjne, double[][] wyjsciaWalidacyjne)
        {
            base.Uczenie(wejscia, wyjscia, wejsciaWalidacyjne, wyjsciaWalidacyjne);
            double[] otrzymaneWyjscia;
            double bladTmp;
            double[][][] gradient = new double[siecNeuronowa.RozmiarSieci][][];
            wspolczynnikUczenia = new double[siecNeuronowa.RozmiarSieci][][];
            prevDelta = new double[siecNeuronowa.RozmiarSieci][][];
            for (int i = 0; i < siecNeuronowa.RozmiarSieci; ++i)
            {
                gradient[i] = new double[siecNeuronowa[i].RozmiarWarstwy][];
                wspolczynnikUczenia[i] = new double[siecNeuronowa[i].RozmiarWarstwy][];
                prevDelta[i] = new double[siecNeuronowa[i].RozmiarWarstwy][];
                for (int j = 0; j < siecNeuronowa[i].RozmiarWarstwy; ++j)
                {
                    gradient[i][j] = new double[siecNeuronowa[i][j].LiczbaWejsc];
                    wspolczynnikUczenia[i][j] = new double[siecNeuronowa[i][j].LiczbaWejsc];
                    prevDelta[i][j] = new double[siecNeuronowa[i][j].LiczbaWejsc];
                    for (int k = 0; k < siecNeuronowa[i][j].LiczbaWejsc; ++k)
                        wspolczynnikUczenia[i][j][k] = monitor.WspolczynnikUczenia;
                }
            }

            bool wyjsciaBipolarne = true;
            for (int i = 0; i < wyjsciaWalidacyjne.Length; ++i)
                for (int j = 0; j < wyjsciaWalidacyjne[i].Length; ++j)
                    if (wyjsciaWalidacyjne[i][j] != 1 && wyjsciaWalidacyjne[i][j] != -1)
                    {
                        wyjsciaBipolarne = false;
                        break;
                    }
            WzrostBleduWalidacyjnegoStrategiaSN wbws;
            if (wyjsciaBipolarne)
                wbws = new WzrostBleduWalidacyjnegoKlasyfikatorStrategiaSN(3, false);
            else
                wbws = new WzrostBleduWalidacyjnegoStrategiaSN(3, false);
            wbws.DaneWalidacyjne = new double[][][] { wejsciaWalidacyjne, wyjsciaWalidacyjne };
            StopMalySpadekStrategia stop = new StopMalySpadekStrategia(
                Math.Min(StopMalySpadekStrategia.domyslnaMinimalnaPoprawa,
                this.monitor.WarunekStopBleduSieci), StopMalySpadekStrategia.domyslnaLiczbaIteracji);
            this.strategie.Clear();
            this.strategie.Add(wbws);
            this.strategie.Add(stop);
            this.Init();
            do
            {
                this.PreIteration();
                bladTmp = 0.0;
                for (int i = 0; i < wejscia.Length; ++i)
                {
                    otrzymaneWyjscia = siecNeuronowa.Uruchom(wejscia[kolejnoscWzorcow[i]]);
                    bladTmp += Monitor.MiaryBledow.SrednioKwadratowySkroconyRefWB(
                        ref wektorBledow, wyjscia[kolejnoscWzorcow[i]], otrzymaneWyjscia);
                    WyliczA(kolejnoscWzorcow[i], gradient);
                }
                UstawWagi(gradient);
                this.monitor.BladSieci = bladTmp / wejscia.Length;
                this.PostIteration();
            }
            while (this.monitor.NumerIteracji < this.monitor.MaxLiczbaIteracji &&
                this.monitor.BladSieci > this.monitor.WarunekStopBleduSieci &&
                this.monitor.UczenieSieci && !stop.ShouldStop());
            StringBuilder strBld = PobierzKomunikat();
            if (wbws.ShouldStop())
            {
                //wzrostBleduWalidacyjnegoStrategia.PrzywrocNajlepszaSiec();
                strBld.AppendLine("uczenie przerwane z powodu trzykrotnego wzrostu błędu walidacyjnego");
            }
            if (stop.ShouldStop())
                strBld.AppendLine("uczenie przerwane z powodu zbyt małych postępów uczenia (błąd malał o mniej niż " +
                    Math.Min(StopMalySpadekStrategia.domyslnaMinimalnaPoprawa, this.monitor.WarunekStopBleduSieci)
                    + " przez " + StopMalySpadekStrategia.domyslnaLiczbaIteracji + " iteracji)");
            this.komunikat = strBld.ToString();
            this.monitor.Logger.SendString(komunikat);
        }

        /// <summary>
        /// wylicza wartosc A dla kazdego z neuronow
        /// </summary>
        /// <param name="i">indeks aktualnie uczonych danych</param>
        /// <param name="gradient">wyznaczane gradienty do poprawy wagi</param>
        protected void WyliczA(int i, double[][][] gradient)
        {
            double sk;
            int l = siecNeuronowa.RozmiarSieci - 1;
            // dla ostatniej warstwy
            for (int j = 0; j < siecNeuronowa[l].RozmiarWarstwy; ++j)
                siecNeuronowa[l][j].A = siecNeuronowa[l][j].PochodnaWyjscia * wektorBledow[j];
            // dla pozostalych warstw
            for (--l; l >= 0; --l)
            {
                //warstwa l-ta
                for (int j = 0; j < siecNeuronowa[l].RozmiarWarstwy; ++j)
                {
                    //neuron j-ty
                    sk = 0.0;
                    for (int k = 0; k < siecNeuronowa[l + 1].RozmiarWarstwy; ++k)
                    {
                        gradient[l+1][k][j] += siecNeuronowa[l][j].Wartosc * siecNeuronowa[l + 1][k].A;
                        sk += siecNeuronowa[l + 1][k].A * siecNeuronowa[l + 1][k][j];
                    }
                    siecNeuronowa[l][j].A = siecNeuronowa[l][j].PochodnaWyjscia * sk;
                }
            }
            //wyznaczam gradient dla wag z warstwy wejsciowej do ukrytej (tutaj l=-1)
            for(int j=0;j<wejscia[i].Length;++j)
                for (int k = 0; k < siecNeuronowa[l + 1].RozmiarWarstwy; ++k)
                    gradient[l + 1][k][j] += wejscia[i][j] * siecNeuronowa[l + 1][k].A;
        }

        /// <summary>
        /// ustawia poprawione wagi
        /// </summary>
        /// <param name="gradient">wyznaczony gradient do poprawy wagi</param>
        protected void UstawWagi(double[][][] gradient)
        {
            for (int j = 0; j < siecNeuronowa.RozmiarSieci; ++j)
            {
                for (int n = 0; n < siecNeuronowa[j].RozmiarWarstwy; ++n)
                {
                    for (int k = 0; k < siecNeuronowa[j][n].LiczbaWejsc; ++k)
                    {
                        siecNeuronowa[j][n][k] += wyliczWspolczynnikUczenia(j, n, k, -gradient[j][n][k],
                            siecNeuronowa[j][n][k] - siecNeuronowa[j][n].PoprzednieWagi[k])
                            + this.monitor.Moment * (siecNeuronowa[j][n][k] - siecNeuronowa[j][n].PoprzednieWagi[k]);
                        gradient[j][n][k] = 0.0d;
                    }
                }
            }
        }



    }

}

